Spring笔记(十二)—— 事务管理之不同方式的使用

编程式的事务管理

Spring 提供了两个模板类 TransactionTemplate 和 PlatformTransactionManager 来支持编程式事务管理,与其他的持久化模板一样,都是线程安全的。一般使用前者,后者类似与 JTA UserTransaction API。

使用 TransactionTemplate

编写一个 TransactionCallback 实现类(通常为匿名内部类),该实现包含需要在事务上下文中执行的代码。然后,将自定义 TransactionCallback 的实例传递给 TransactionTemplate 的 execute() 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class SimpleService implements Service {
// single TransactionTemplate shared amongst all methods in this instance
private final TransactionTemplate transactionTemplate;
// use constructor-injection to supply the PlatformTransactionManager
public SimpleService(PlatformTransactionManager transactionManager) {
Assert.notNull(transactionManager, "The 'transactionManager' argument must not be null.");
this.transactionTemplate = new TransactionTemplate(transactionManager);
}
public Object someServiceMethod() {
return transactionTemplate.execute(new TransactionCallback() {
// the code in this method executes in a transactional context
public Object doInTransaction(TransactionStatus status) {
updateOperation1();
return resultOfUpdateOperation2();
}
});
}
}

如果执行事务回调的时候没有返回值,则使用 TransactionCallbackWithoutResult 代替 TransactionCallback,且可以调用 TransactionStatus 对象的 setRollbackOnly() 方法回滚事务:

1
2
3
4
5
6
7
8
9
10
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
updateOperation1();
updateOperation2();
} catch (SomeBusinessExeption ex) {
status.setRollbackOnly();
}
}
});

TransactionTemplate 的事务设置可以通过编程方式或配置文件指定,如传播方式,隔离级别,超时等,TransactionTemplate 实例默认情况下具有默认事务设置。

  • 编程方式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class SimpleService implements Service {
    private final TransactionTemplate transactionTemplate;
    public SimpleService(PlatformTransactionManager transactionManager) {
    Assert.notNull(transactionManager, "The 'transactionManager' argument must not be null.");
    this.transactionTemplate = new TransactionTemplate(transactionManager);
    // the transaction settings can be set here explicitly if so desired
    this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
    this.transactionTemplate.setTimeout(30); // 30 seconds
    // and so forth...
    }
    }
  • 配置文件方式:

    1
    2
    3
    4
    5
    <bean id="sharedTransactionTemplate"
    class="org.springframework.transaction.support.TransactionTemplate">
    <property name="isolationLevelName" value="ISOLATION_READ_UNCOMMITTED"/>
    <property name="timeout" value="30"/>
    </bean>"

使用 PlatformTransactionManager

使用 PlatformTransactionManager 直接管理事务,使用 TransactionDefinition 和 TransactionStatus 对象启动,回滚和提交事务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// explicitly setting the transaction name is something that can only be done programmatically
def.setName("SomeTxName");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = txManager.getTransaction(def);
try {
// execute your business logic here
}
catch (MyException ex) {
txManager.rollback(status);
throw ex;
}
txManager.commit(status);

使用 XML 配置声明式事务管理

使用声明式事务管理的方式,对代码的侵入性最小,可以让事务管理代码完全从业务代码中移除,非常符合非侵入式轻量级容器的概念。Spring 的声明式事务管理是通过 Spring AOP 实现的,通过事务的声明性信息,Spring 负责将事务管理增强逻辑动态织入到业务方法相应的连接中。这些逻辑包括获取线程绑定资源、开始事务、提交/回滚事务、进行异常转换和处理等工作。
Spring 提供了和 EJB CMT 相似的声明式事务管理,这不但体现在两者的最终执行效果上,还体现在两个声明事务的语法上,即便如此,两者仍存在明显的不同:

比较项 EJB CMT Spring
是否绑定 JTA 绑定在 JTA 上,即便是单数据源,所以 EJB 不能脱离容器运行 可以在任何环境下使用,包括直接在 Spring 中声明的数据源或应用服务器 JNDI 中的 JTA 数据源
持久化技术支持 采用非开发放的 EJB 自制持久化技术 通过少量配置即可和 JDBC、JDO、Hibernate 等持久化技术一起工作
目标类要求 必须是实现特定接口的特殊类 可以是任何 POJO,不过在内部必须使用资源获取工具类操作数据连接或会话,如果 DAO 使用模板类进行构建,这种要求将自动得到满足
回滚规则 没有 提供声明式的回滚规则
开放性控制 使用 EJB CMT,除了使用 setRollbackOnly(),没有办法影响容器的事务管理 Spring 允许用户通过 AOP 定制事务行为。如:用户可以在事务回滚中插入定制的行为,也可以增加任意的增强,就和任何 AOP 的增强一样
分布式事务 支持分布式事务,一般应用并不需要使用这样的功能 Spring 不直接支持高端应用服务器所提供的跨越远程调用的事务上下文传播。此时可以通过 Spring 的 Java EE 服务集成来提供。此外,如果在 Spring 中集成 JOTM 后,Spring 也可以提供 JTA 事务的功能

示例

FooService 是业务层的接口(需要实施事务增强的服务接口),通过 Spring 的声明事务可以让这个接口的方法拥有是适合的事务功能。

1
2
3
4
5
6
7
8
// 需要实施事务增强的服务接口
package x.y.service;
public interface FooService {
Foo getFoo(String fooName);
Foo getFoo(String fooName, String barName);
void insertFoo(Foo foo);
void updateFoo(Foo foo);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 上述接口实现
package x.y.service;
public class DefaultFooService implements FooService {
public Foo getFoo(String fooName) {
throw new UnsupportedOperationException();
}
public Foo getFoo(String fooName, String barName) {
throw new UnsupportedOperationException();
}
public void insertFoo(Foo foo) {
throw new UnsupportedOperationException();
}
public void updateFoo(Foo foo) {
throw new UnsupportedOperationException();
}
}

假设 FooService 接口的前两个方法 getFoo(String) 和 getFoo(String,String) 必须在具有只读语义的事务上下文中执行,而其他方法 insertFoo(Foo) 和 updateFoo(Foo) 必须在具有读写语义的事务上下文中执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<!-- 文件'context.xml' -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置事务用的 Service 对象 -->
<bean id="fooService" class="x.y.service.DefaultFooService"/>
<!-- 事务增强 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<!-- 事务属性定义 -->
<tx:attributes>
<!-- 所有以'get'开头的方法都是只读的 -->
<tx:method name="get*" read-only="true"/>
<!-- other methods use the default transaction settings (see below) -->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- 使用强大的切点表达式语言定义目标方法 -->
<aop:config>
<!-- 通过 aop 定义事务增强切面 -->
<aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>
<!-- 引用事务增强 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
</aop:config>
<!-- don't forget the DataSource -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
<property name="username" value="scott"/>
<property name="password" value="tiger"/>
</bean>
<!-- 事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- other <bean/> definitions here -->
</beans>

根据上述配置,我们需要一个 Service 对象,fooService bean,事务。要应用的事务语义封装在 定义中,<tx:advice /> 中表示所有以’get’开头的方法都是只读的,所有其他方法都将使用默认事务语义。<tx:advice /> 标签的 transaction-manager 属性为事务管理器 PlatformTransactionManager bean 的名称(在本例中为 txManager bean),如果事务管理器命名为 transactionManager,则可以不指定 transaction-manager 属性。
<aop:config /> 确保 由 txAdvice bean 定义的事务增强 能在程序中的适当位置执行。首先定义一个切点,该切点匹配 FooService 接口(fooServiceOperation)中定义的所有方法。然后使用 advisor 将切点与 txAdvice 关联,在执行 fooServiceOperation 时,将运行由 txAdvice 定义的增强。
上述配置将用于围绕从 fooService bean 定义创建的对象创建事务代理。使用事务增强配置代理,以便在代理上调用适当的方法时,根据与该方法关联的事务配置,将事务启动,挂起,标记为只读。

1
2
3
4
5
6
7
public final class Boot {
public static void main(final String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("context.xml", Boot.class);
FooService fooService = (FooService) ctx.getBean("fooService");
fooService.insertFoo (new Foo());
}
}

回滚 XML 配置声明式事务

在 Spring 框架中,事务回滚的推荐方法为:从当前在事务上下文中执行的代码中抛出异常。Spring 框架的事务基础结构代码将捕获任何未处理的异常,并确定是否将事务标记为回滚。在其默认配置中,Spring框架的事务基础结构代码只在发生 RuntimeException 和 unchecked 异常的情况下标记用于回滚的事务(即当抛出的异常是 RuntimeException 的实例或子类时);从事务方法抛出的 checked 异常不会导致在默认配置中回滚。
我们可以自己配置通过哪些异常类型标记事务以进行回滚(包括检查异常),以下为 XML 配置的代码:

1
2
3
4
5
6
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true" rollback-for="NoProductInStockException"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>

也可以指定不进行事务回滚的异常,这样当应用程序抛出该异常时,不进行事务回滚:

1
2
3
4
5
6
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>

如下 XML 配置表示除了 InstrumentNotFoundException 之外,其余的异常都进行回滚:

1
2
3
4
5
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="*" rollback-for="Throwable" no-rollback-for="InstrumentNotFoundException"/>
</tx:attributes>
</tx:advice>

tx:advice 标签的属性

事务的设置可以使用 <tx:advice/> 标签指定,其默认的 <tx:advice/> 设置为:

  • 传播行为:REQUIRED;
  • 隔离级别:DEFAULT;
  • 读写事务属性:读/写事务;
  • 超时时间:默认为基础事务系统的默认超时,如果不支持超时,则为无;
  • 回滚设置:任意 RuntimeException 会触发回滚,而 checked Exception 不会。

<tx:method/> 的设置:

属性 是否必须 默认值 描述
name 与事务关联的方法名,可使用通配符* gethandle
propagation REQUIRED 事务传播行为,可选值:REQUIRED、SUPPORTS、MANDATORY、REQUIRES_NEW、NOT_SUPPORTED、NEVER、NESTED
isolation DEFAULT 事务隔离级别,可选值:DEFAULT、READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ、SERIALIZABLE
timeout -1 事务超时的时间(以秒为单位),如果设置为 -1,事务超时的时间由底层的事务系统决定
read-only false 事务是否只读
rollback-for 所有 RuntimeException 触发事务回滚的 Exception,用异常名称的片断进行匹配,可以设置多个,以逗号分开
no-rollback-for 所有 checked Exception 不触发事务回滚的 Exception,用异常名称的片断进行匹配,可以设置多个,以逗号分开

使用注解配置声明式事务管理

除了基于 XML 的事务配置之外,Spring 还提供了基于注解的事务配置,即通过 @Transactional 对需要事务增强的 Bean 接口、实现类或方法进行标注,在容器中配置基于注解的事务增强驱动,即可启动基于注解的声明式事务。

使用 @Transactional 注解

使用 @Transactional 注解的将要实现事务增强的实现类:

1
2
3
4
5
6
7
8
// the service class that we want to make transactional
@Transactional
public class DefaultFooService implements FooService {
Foo getFoo(String fooName);
Foo getFoo(String fooName, String barName);
void insertFoo(Foo foo);
void updateFoo(Foo foo);
}

当上述的 POJO 在 Spring IOC 容器中定义为 Bean 时,可以通过仅添加一行 XML 配置来为 Bean 实例实现事务增强:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!-- from the file 'context.xml' -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 由于该 Bean 实现类标注了 @Transactional,所以将会被 tx:annotation-driven 标签的注解驱动自动织入事务 -->
<bean id="fooService" class="x.y.service.DefaultFooService"/>
<!-- 对标注 @Transactional 注解的 Bean 进行加工处理,以织入事务管理切面 -->
<tx:annotation-driven transaction-manager="txManager"/>
<!-- a PlatformTransactionManager is still required -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- (this dependency is defined somewhere else) -->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- other <bean/> definitions here -->
</beans>

默认情况下,<tx:annotation-driven> 会自动使用名称为 transactionManager 的事务管理器,所以如果事务管理器的 id 为 transactionManager 时,可以简化为 <tx:annotation-driven/>
<tx:annotation-driven/> 有四个属性:

  • transaction-manager:其 Annotation 属性为 TransactionManagementConfigurer,默认值为 transactionManager。
  • mode:其 Annotation 属性为 mode,默认值为 proxy,默认模式 proxy 使用 Spring 的 AOP 框架处理要被代理的带注释的 bean。替代模式 aspectj 改为用 Spring 的 AspectJ 事务切面编织受影响的类,修改目标类字节码以用于任意方法的调用。AspectJ 编织需要在类路径中使用 spring-aspects.jar 以及启用加载时编织(或编译时编织)。
  • proxy-target-class:其 Annotation 属性为 proxyTargetClass,默认值为 false。如果为 true,Spring 将通过子类来代理业务类;为 false,则使用基于接口的代理,需要在类路径中添加 CGLib.jar 类库。
  • order:其 Annotation 属性为 order,其默认值为 Ordered.LOWEST_PRECEDENCE。如果业务类除事务切面外,还需要织入其他的切面,通过该属性可以控制事务切面在目标连接点的织入顺序。

@Transactional 的属性

@Transactional 的默认属性:

  • 传播行为:PROPAGATION_REQUIRED;
  • 隔离级别:ISOLATION_DEFAULT;
  • 读写事务属性:读/写事务;
  • 超时时间:默认为基础事务系统的默认超时,如果不支持超时,则为无;
  • 回滚设置:任意 RuntimeException 会触发回滚,而 checked Exception 不会。

@Transactional 属性说明:

  • value:String 类型,指定要使用的事务管理器的可选限定符。
  • propagation:事务传播行为,通过枚举类(org.springframework.transaction.annotation.Propagation)提供合法值;如:@Transactional(propagation=Propagation.REQUIRES_NEW)。
  • isolation:事务隔离级别,通过枚举类(org.springframework.transaction.annotation.Isolation)提供合法值;如:@Transactional(isolation=Isolation.READ_COMMITTED)。
  • readOnly:事务读写性,boolean 型;如:@Transactional(readOnly=true)。
  • timeout:超时时间,int 型,以秒为单位;如:@Transactional(timeout=0)。
  • rollbackFor:一组异常类,遇到时进行回滚,类型为 Class<? extends Throwable>[],默认为{};如:@Transactional(rollbackFor={SQLException.class}),多个异常之间可用逗号分隔。
  • rollbackForClassName:一组异常类,遇到时进行回滚,类型为 String[],默认为{};如:@Transactional(rollbackForClassName={“Exception”})。
  • noRollbackFor:一组异常类,遇到时不回滚,类型为 Class<? extends Throwable>[],默认为{}。
  • noRollbackForClassName:一组异常类,遇到时不回滚,类型为 String[],默认为{}。

@Transactional 使用不同的事务管理器

大多数情况下,一个应用仅需一个事务管理器,如果希望在不同的地方使用不同的事务管理器,可如下实现:

1
2
3
4
5
6
7
8
9
public class TransactionalService {
// 使用名为 order 的事务管理器
@Transactional("order")
public void setSomething(String name) { ... }
// 使用名为 account 的事务管理器
@Transactional("account")
public void doSomething() { ... }
}

order 和 account 事务管理器在 XML 中的定义:

1
2
3
4
5
6
7
8
9
10
<tx:annotation-driven/>
<bean id="transactionManager1" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
...
<qualifier value="order"/><!-- 为事务管理器标识一个名字 -->
</bean>
<bean id="transactionManager2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
...
<qualifier value="account"/>
</bean>

在一两处使用带标识的 @Transactional 也许挺合适,但是如果到处都使用,则显得繁琐。可以自定义一个绑定到特定事务管理器的注解,然后直接使用这个自定义的注解进行标识:

1
2
3
4
5
6
7
8
9
10
11
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional("order")
public @interface OrderTx {
}
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional("account")
public @interface AccountTx {
}

按相似的方法,还可以定义一个绑定到 order 事务管理器的 @OrderTx,完成定义后,就可以用以下方式对原来的代码进行调整了。

1
2
3
4
5
6
7
8
public class TransactionalService {
@OrderTx
public void setSomething(String name) { ... }
@AccountTx
public void doSomething() { ... }
}
}

参考资料:

Spring 3.x 企业应用开发实战
Spring Framework Reference Documentation

热评文章